#include "cmdline.h"

// Rev 2

#include <list>
#include <iostream>
#include <string>
#include <iomanip>
#include <sstream>

#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

namespace cmdline {


CommandlineParser::CommandlineParser() : max_unnamed_params(0)
{
}

CommandlineParser::~CommandlineParser() {
	foreach(Option* option, options) {
		delete option;
	}
	foreach(Parameter *param, parameters) {
		delete param;
	}
}

void CommandlineParser::addOption(wchar_t charflag, std::wstring strflag, bool params,
								std::wstring description)
{
	options.push_back(new Option(charflag, strflag, params, description));
}

void CommandlineParser::setMaxUnnamedParams(int max) {
	max_unnamed_params = max;
}

void CommandlineParser::parse(int argc, char *argv[]) {
	for(int i=1; i<argc; ++i) {
		params_wstr.push_back(wstr::toWide(argv[i]));
		params_str.push_back(argv[i]);
	}

	std::list<Parameter*> queued_flags;

	bool parsing_flags = true;


	for(unsigned int i=0; i<params_wstr.size(); ++i) {
		if(queued_flags.empty()) {
			// No option is waiting for a parameter at the moment
			if(parsing_flags && isSingleCharacterFlag(params_wstr[i])) { // -abcd
				// Add all encountered single char flags to queued_flags
				foreach(wchar_t cflag, getSingleCharacterFlags(params_wstr[i])) {
					Option *option = findOption(cflag);
					if(!option) throw UnknownParamException(cflag);

					Parameter *param = new Parameter(option);
					if(option->params) queued_flags.push_back(param);
					parameters.push_back(param);
				}
			} else if(parsing_flags && isMultiCharacterFlag(params_wstr[i])) { // --foo
				// Add the encountered string flag to queued_flags
				std::wstring flag = getMultiCharacterFlag(params_wstr[i]);
				Option *option = findOption(flag);
				if(!option) throw UnknownParamException(flag);

				Parameter *param = new Parameter(option);
				if(option->params) queued_flags.push_back(param);
				parameters.push_back(param);
			} else if(parsing_flags && params_wstr[i] == L"-") {
				// Stop parsing more flags
				parsing_flags = false;
			} else {
				// Unnamed parameter
				if(max_unnamed_params == -1 || (int)unnamedparams_wstr.size() < max_unnamed_params)
				{
					unnamedparams_wstr.push_back(params_wstr[i]);
					unnamedparams_str.push_back(params_str[i]);
				}
				else
				{
					throw UnexpectedParamException(params_wstr[i]);
				}					
			}
		} // if queued_flags empty
		else
		{
			// Fill queued flags
			Parameter *param = queued_flags.front();
			queued_flags.pop_front();
			param->wstr = params_wstr[i];
			param->str = params_str[i];
		}
	}

	if(!queued_flags.empty()) {
		throw ValueNotAssignedException(queued_flags.front()->option->strflag);
	}

}

std::vector<Parameter*> &CommandlineParser::getParams() {
	return parameters;
}

std::vector<std::wstring> &CommandlineParser::getUnnamedParams_wstr() {
	return unnamedparams_wstr;
}

std::vector<std::string> &CommandlineParser::getUnnamedParams_str() {
	return unnamedparams_str;
}

bool CommandlineParser::isSingleCharacterFlag(std::wstring param) {
	if(param.length() >= 2 && param[0] == L'-' && param[1] != L'-') {
		return true;
	} else {
		return false;
	}
}

std::vector<wchar_t> CommandlineParser::getSingleCharacterFlags(std::wstring param) {
	std::vector<wchar_t> flags;
	for(unsigned int i=1; i<param.length(); ++i) {
		flags.push_back(param[i]);
	}
	return flags;
}

bool CommandlineParser::isMultiCharacterFlag(std::wstring param) {
	if(param.length() >= 2 && param[0] == L'-' && param[1] == L'-') {
		return true;
	} else {
		return false;
	}
}

std::wstring CommandlineParser::getMultiCharacterFlag(std::wstring param) {
	return param.substr(2, param.npos);
}

Option *CommandlineParser::findOption(wchar_t charflag) {
	foreach(Option *option, options) {
		if(charflag == option->charflag) return option;
	}
	return NULL;
}

Option *CommandlineParser::findOption(std::wstring strflag) {
	foreach(Option *option, options) {
		if(strflag == option->strflag) return option;
	}
	return NULL;
}


std::wstring CommandlineParser::help() {
	std::wostringstream info;
	foreach(Option *option, options) {
		std::wstring cflag = option->charflag == L'-' ? L"" : L"-" + std::wstring(1, option->charflag);
		std::wstring sflag = L"--" + option->strflag;
		std::wstring desc = option->description;
		info <<	std::setw(4) << std::right << cflag << L"  "
			<< std::setw(18) << std::left << sflag
			<< L"\t" << desc << std::endl;
	}
	return info.str();
}

}